#!/bin/bash

cleanup() {
    kill $torpid
    rm -r "$datadir"
    [[ "$file" != "" ]] && rm "$file"
}
usage() {
[[ "$@" != "" ]] && echo "$@"
cat <<EOF

Wrapper to start tor before starting applications with torsocks.
Looks for free ports and starts a new instance of tor.

Usage: $me [${me}_options] -- command [command_options]
Options:

-C      Use a control port which is always one higher than the listening port.

-f str  Save port and controlport to this file, in shell-sourcable format.

-E      Use a selection of European entry/exit nodes: $Ecountries

-c str  List of countries to use (note the format above).

The default is to not restrict the choice of entry/exit nodes.

-P int  Set Tor SOCKS localhost listening port. Default: $port

-t int  Try higher ports if chosen port is in use.
        Increase port by 1 for [int] times before giving up.
        This option checks ports with fuser The default is to try 100 times.
        If set to 0 (zero) higher ports won't be tried.

-r      Loop retry, each time with a new tor connection
        (requires interactive terminal).

-q      Be quiet. Only the launched application speaks.

-d      Set delay to sleep (in seconds) after starting tor. Default: $delay

~/.config/tor/ must be writeable.

EOF
exit 1
}
log() {
    [[ "$quiet" == '' ]] && echo -e "$@" >&2
}
trap cleanup EXIT

me="${0##*/}"

Ecountries="{fi},{ax},{se},{no},{dk},{ee},{lv},{lt},{cz},{sk},{at},{si},{it},{ch},{fr},{es},{pt},{be},{nl},{de},{gb},{ie},{is},{gr}"
countries=""
declare -i port=9222 # declare as integer
try=100 # try higher ports so many times
retry=0
delay=1
quiet=''
torpid=bla
file=''

# character for separator line
sepchar="-"
sep=''
columns="$(tput cols)"
[ -z "$columns" ] && columns=80
# adjust separator width to terminal width
for ((i=0;i<columns;i++)); do sep="$sep$sepchar"; done
unset sepchar columns
controlport=false

while getopts "c:CEP:t:rqd:h" opt; do
  case $opt in
    c)
      countries="$OPTARG"
      ;;
    C)
      controlport=true
      ;;
    E)
      countries="$Ecountries"
    ;;
    f)
      file="$OPTARG"
      [ -r "$file" ] && usage "File $file already exists. Please remove it."
      touch "$file" || usage "Cannot touch $file. Permissions?"
    ;;
    P) [[ "$OPTARG" =~ [0-9]+ ]] && (( OPTARG >= 0 )) && (( OPTARG <= 65535 )) || usage "Invalid port: $OPTARG"
       port="$OPTARG"
    ;;
    t) try="$OPTARG"
    ;;
    r) retry=1
    ;;
    q) quiet='--quiet'
    ;;
    d) delay="$OPTARG"
    ;;
    *|h) usage
      ;;
  esac
done

shift $((OPTIND-1))
unset Ecountries

# simple dependency check
for dep in fuser tor kill torsocks; do
	type -f $dep >/dev/null || usage
done

### Retry Loop
while :; do
    now="$(printf "%(%H:%M:%S)T")"
    [[ "$controlport" == true ]] && controlport=$((port+1))

    if (( try != 0 )); then
      [[ "$try" =~ [0-9]+ ]] && (( try <= (65535-port) )) || usage "Invalid number: $try (Must be between 1 and $((65535-port))."
      # find a free port
      i=0
      for ((;i<=try;i++)); do
        fuser -sn tcp "$((port+i))" && continue
        [[ "$controlport" != false ]] && fuser -sn tcp "$((controlport+i))" && continue
        break
      done
      (( i > try )) && usage "Could not find a free port between $port and $((port+try))"
      port=$((port+i))
      [[ "$controlport" != false ]] && controlport=$((controlport+i))
    else
      fuser -sn tcp "$port" && usage "Port $port is not free."
    fi

    ### prepare tor ###
    log "Tor data dir: $HOME/.config/tor/${me}.${now}.${port}"
    datadir="$HOME/.config/tor/${me}.${now}.${port}"
    torrc="$datadir/torrc"
    rm -rf "$datadir"
    mkdir -p "$datadir" || usage "Cannot mkdir $datadir"
    if [[ "$countries" == "" ]]; then
        printf "SocksPort $port\nDataDirectory \"$datadir\"\nRunAsDaemon 1\nPidFile \"$datadir/pid\"\n" > "$torrc"
        log "Tor: no restrictions on choice of nodes\n$sep"
    else
        printf "EntryNodes $countries\nExitNodes $countries\nSocksPort $port\nDataDirectory \"$datadir\"\nRunAsDaemon 1\nPidFile \"$datadir/pid\"\n" > "$torrc"
        log "Tor: nodes only from these countries:\n$countries\n$sep"
    fi
    [[ "$controlport" != false ]] && printf "ControlPort $controlport\nCookieAuthentication 0\n" >> "$torrc"

    ### launch tor ###
    tor $quiet -f "$torrc" >&2
    ###
    [ -r "$datadir/pid" ] && torpid="$(<"$datadir/pid")"
    [[ "$torpid" == 0 ]] && usage "Apparently tor did not start; $datadir/pid is empty or does not exist."
    [[ "$file" != "" ]] && printf "port=%s\ncontrolport=%s\n" "$port" "$controlport" > "$file"
    log "$sep\nTor pid: $torpid\n$sep\nWaiting ${delay}s before continuing..."

    sleep "$delay"

    ### launch the script ###
    if [[ "$@" != "" ]] && [ -x "$1" ]; then
        log "$sep\nLaunching: torsocks $quiet -i -P $port $@\n$sep"
        torsocks $quiet -i -P "$port" "$@"
    else
        echo "Tor is now running on port $port."
        [[ "$controlport" != false ]] && echo "Control port is $controlport."
    fi
    ###
    if [[ "$retry" == 1 ]]; then
        read -p "Do you want to retry (r)? " answer
        [[ "$answer" != r ]] && exit 0
    fi
    cleanup
    echo -e "$sep\n###### Retrying #########\n$sep"
    clear
done
